/******************************************************************************* 
 * Copyright (c) 2008 xored software, Inc.  
 * 
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution, and is available at 
 * http://www.eclipse.org/legal/epl-v10.html  
 * 
 * Contributors: 
 *     xored software, Inc. - initial API and Implementation (Yuri Strot) 
 *******************************************************************************/
package com.xored.glance.ui.panels;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;

import com.xored.glance.internal.ui.GlancePlugin;
import com.xored.glance.internal.ui.panels.CheckAction;
import com.xored.glance.internal.ui.preferences.IPreferenceConstants;
import com.xored.glance.internal.ui.search.SearchRule;
import com.xored.glance.ui.sources.Match;
import com.xored.glance.ui.utils.SelectionAdapter;
import com.xored.glance.ui.utils.UIUtils;

/**
 * @author Yuri Strot
 * 
 */
public abstract class SearchPanel implements ISearchPanel,
		IPreferenceConstants, IPropertyChangeListener {

	/**
	 * @param parent
	 * @param style
	 */
	public SearchPanel() {
		rule = new SearchRule("");
		getPreferences().addPropertyChangeListener(this);
	}

	public void propertyChange(PropertyChangeEvent event) {
		String property = event.getProperty();
		if (property != null && property.startsWith(SEARCH_PREFIX)) {
			updateRule();
		}
	}

	public void createContent(Composite parent) {
		Composite container = createContainer(parent);
		GridLayout layout = new GridLayout(3, false);
		layout.verticalSpacing = 0;
		layout.marginHeight = 2;
		layout.marginWidth = 0;
		container.setLayout(layout);
		createIcon(container);
		createText(container, SWT.BORDER);
		createToolBar(container);
		initSize(container);
	}

	public Control getControl() {
		return container;
	}

	protected Composite createContainer(Composite parent) {
		container = new Composite(parent, SWT.NONE);
		return container;
	}

	public void firstFound(final Match match) {
		UIUtils.asyncExec(title, new Runnable() {
			public void run() {
				setBackground(match != null);
			}
		});
	}

	public void allFound(Match[] matches) {
		result = matches;
		UIUtils.asyncExec(title, new Runnable() {
			public void run() {
				setBackground(result.length > 0);
			}
		});
	}

	public void finished() {
	}

	protected Label createIcon(Composite parent) {
		final Label label = new Label(parent, SWT.NONE);
		label.setImage(GlancePlugin.createImage(GlancePlugin.IMG_SEARCH));
		label.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				label.getImage().dispose();
			}
		});
		return label;
	}

	protected Control createText(Composite parent, int style) {
		title = new Combo(parent, style | SWT.DROP_DOWN);
		String text = rule.getText();
		title.setText(text);
		loadHistory();
		if (text != null && text.length() > 0 && result.length == 0)
			setBackground(false);
		title.addModifyListener(modifyListener);
		title.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == '\r') {
					if (e.stateMask == 0)
						fireFindNext();
					else if (e.stateMask == SWT.SHIFT)
						fireFindPrevious();
				}
				if (e.keyCode == SWT.ESC && e.stateMask == 0) {
					closePanel();
				}
			}
		});
		title.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		return title;
	}

	protected ToolBar createToolBar(Composite parent) {
		ToolBar bar = new ToolBar(parent, SWT.FLAT);
		GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(bar);
		if (getPreferences().getBoolean(PANEL_DIRECTIONS)) {
			createNextItem(bar);
			createPreviousItem(bar);
		}
		createSettingsMenu(bar);
		if (getPreferences().getBoolean(PANEL_CLOSE)) {
			createClose(bar);
		}
		return bar;
	}

	protected ToolItem createNextItem(ToolBar bar) {
		bNext = createTool(bar, "Next (Enter)", GlancePlugin.IMG_NEXT,
				new SelectionAdapter() {
					@Override
					public void selected(SelectionEvent e) {
						fireFindNext();
					}
				});
		return bNext;
	}

	protected ToolItem createPreviousItem(ToolBar bar) {
		bPrev = createTool(bar, "Previous (Enter+Shift)",
				GlancePlugin.IMG_PREV, new SelectionAdapter() {
					@Override
					public void selected(SelectionEvent e) {
						fireFindPrevious();
					}
				});
		return bPrev;
	}

	private ToolItem createTool(ToolBar bar, String tip, String image,
			SelectionListener listener) {
		final ToolItem item = new ToolItem(bar, SWT.PUSH);
		item.setToolTipText(tip);
		item.setImage(GlancePlugin.createImage(image));
		item.addSelectionListener(listener);
		item.addDisposeListener(new DisposeListener() {

			public void widgetDisposed(DisposeEvent e) {
				item.getImage().dispose();
			}
		});
		return item;
	}

	protected ToolItem createSettingsMenu(ToolBar bar) {
		ToolItem settings = new ToolItem(bar, SWT.PUSH);
		settings.setImage(JFaceResources.getImage(PopupDialog.POPUP_IMG_MENU));
		settings.setDisabledImage(JFaceResources
				.getImage(PopupDialog.POPUP_IMG_MENU_DISABLED));
		settings.setToolTipText("Settings"); //$NON-NLS-1$
		settings.addSelectionListener(new SelectionAdapter() {
			@Override
			public void selected(SelectionEvent e) {
				showSettings();
			}
		});
		return settings;
	}

	protected ToolItem createClose(ToolBar bar) {
		ToolItem close = new ToolItem(bar, SWT.PUSH);
		ImageDescriptor image = PlatformUI.getWorkbench().getSharedImages()
				.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE);
		if (image != null)
			close.setImage(image.createImage());
		close.setToolTipText("Close"); //$NON-NLS-1$
		close.addSelectionListener(new SelectionAdapter() {
			@Override
			public void selected(SelectionEvent e) {
				closePanel();
			}
		});
		return close;
	}

	protected void showSettings() {
		MenuManager manager = new MenuManager();
		fillMenu(manager);
		Menu menu = manager.createContextMenu(getControl());

		Point location = getControl().getDisplay().getCursorLocation();
		menu.setLocation(location);
		menu.setVisible(true);
	}

	protected void fillMenu(IMenuManager menu) {
		menu.add(new Separator());
		newAction(menu, SEARCH_CASE_SENSITIVE, LABEL_CASE_SENSITIVE, true);
		boolean regExp = newAction(menu, SEARCH_REGEXP, LABEL_REGEXP, true)
				.isChecked();
		newAction(menu, SEARCH_CAMEL_CASE, LABEL_CAMEL_CASE, !regExp);
		newAction(menu, SEARCH_WORD_PREFIX, LABEL_WORD_PREFIX, !regExp);
		menu.add(new Separator());
		menu.add(new Action("Preferences...") {
			@Override
			public void run() {
				PreferencesUtil.createPreferenceDialogOn(container.getShell(),
						PREFERENCE_PAGE_ID, null, null).open();
			}
		});
	}

	private CheckAction newAction(IMenuManager menu, String name, String label,
			boolean enable) {
		return newAction(menu, name, label, enable, null);
	}

	private CheckAction newAction(IMenuManager menu, String name, String label,
			boolean enable, String path) {
		CheckAction action = new CheckAction(name, label);
		if (path != null) {
			action.setImageDescriptor(GlancePlugin.getImageDescriptor(path));
		}
		action.setEnabled(enable);
		menu.add(action);
		return action;
	}

	protected void textChanged() {
		String text = title.getText();
		boolean empty = text.length() == 0;
		if (empty)
			textEmpty();
		if (bNext != null && !bNext.isDisposed())
			bNext.setEnabled(!empty);
		if (bPrev != null && !bPrev.isDisposed())
			bPrev.setEnabled(!empty);
		updateRule();
	}

	protected void textEmpty() {
		setBackground(true);
	}

	protected void updateRule() {
		rule = new SearchRule(title.getText());
		fireRuleChanged(rule);
	}

	/**
	 * @return the rule
	 */
	public SearchRule getRule() {
		return rule;
	}

	public void setFocus(String text) {
		if (isReady()) {
			if (text == null || text.length() == 0)
				text = rule.getText();
			if (text != null && text.length() > 0) {
				title.setText(text);
				title.setSelection(new Point(0, text.length() - 1));
				textChanged();
			}
			title.forceFocus();
		}
	}

	public void setEnabled(boolean enabled) {
		if (isReady()) {
			title.setEnabled(enabled);
		}
	}

	private boolean isReady() {
		return title != null && !title.isDisposed();
	}

	public void addPanelListener(ISearchPanelListener listener) {
		listeners.add(listener);
	}

	public void removePanelListener(ISearchPanelListener listener) {
		listeners.remove(listener);
	}

	private void fireRuleChanged(SearchRule rule) {
		historyDirty = true;
		Object[] objects = listeners.getListeners();
		for (Object object : objects) {
			ISearchPanelListener listener = (ISearchPanelListener) object;
			listener.ruleChanged(rule);
		}
	}

	private void fireFindNext() {
		updateHistory();
		Object[] objects = listeners.getListeners();
		for (Object object : objects) {
			ISearchPanelListener listener = (ISearchPanelListener) object;
			listener.findNext();
		}
	}

	private void fireFindPrevious() {
		updateHistory();
		Object[] objects = listeners.getListeners();
		for (Object object : objects) {
			ISearchPanelListener listener = (ISearchPanelListener) object;
			listener.findPrevious();
		}
	}

	protected void fireClose() {
		updateHistory();
		saveHistory();
		Object[] objects = listeners.getListeners();
		for (Object object : objects) {
			ISearchPanelListener listener = (ISearchPanelListener) object;
			listener.close();
		}
		getPreferences().removePropertyChangeListener(this);
	}

	/**
	 * @return the preferedWidth
	 */
	protected int getPreferedWidth() {
		return preferredWidth;
	}

	/**
	 * @return the preferredHeight
	 */
	protected int getPreferredHeight() {
		return preferredHeight;
	}

	private void initSize(Composite composite) {
		Point size = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		preferredWidth = size.x;
		preferredHeight = size.y;
		preferredWidth -= title.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
		int widthInChars = getPreferences().getInt(PANEL_TEXT_SIZE);
		preferredWidth += getTextWidth(title, widthInChars) + 15;
	}

	protected int getTextWidth(Control control, int width) {
		GC gc = new GC(control);
		try {
			gc.setFont(title.getFont());
			return gc.getFontMetrics().getAverageCharWidth() * width;
		} finally {
			gc.dispose();
		}
	}

	/**
	 * Updates history.
	 */
	private void updateHistory() {
		if (title != null && !title.isDisposed() && historyDirty) {
			title.removeModifyListener(modifyListener);
			try {
				String findString = title.getText();
				Point sel = title.getSelection();
				findString = fixItem(findString);
				if (findString != null) {
					findHistory.remove(findString);
					findHistory.add(0, findString);
					title.removeAll();
					for (String string : findHistory) {
						title.add(string);
					}
					title.setText(findString);
					title.setSelection(sel);
				}
			} finally {
				title.addModifyListener(modifyListener);
				historyDirty = false;
			}
		}
	}

	private void saveHistory() {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < findHistory.size() && i < 8; i++) {
			String item = findHistory.get(i);
			if (i > 0) {
				buffer.append("\n");
			}
			buffer.append(item);
		}
		getPreferences().putValue(HISTORY, buffer.toString());
	}

	private void loadHistory() {
		findHistory = new ArrayList<String>();
		String content = getPreferences().getString(HISTORY);
		String[] items = content.split("\n");
		for (String item : items) {
			findHistory.add(item);
			title.add(item);
		}
	}

	private String fixItem(String item) {
		if (item.length() == 0) {
			return null;
		}
		int index = item.indexOf("\n");
		if (index == 0) {
			return null;
		} else if (index > 0) {
			return item.substring(0, index);
		} else {
			return item;
		}
	}

	private IPreferenceStore getPreferences() {
		return GlancePlugin.getDefault().getPreferenceStore();
	}

	protected void setBackground(boolean found) {
		title.setBackground(found ? GOOD_COLOR : BAD_COLOR);
	}

	protected static final Color GOOD_COLOR = Display.getDefault()
			.getSystemColor(SWT.COLOR_WHITE);
	protected static final Color BAD_COLOR = new Color(Display.getDefault(),
			255, 102, 102);

	protected Composite container;
	protected Combo title;

	private ListenerList listeners = new ListenerList();
	private ModifyListener modifyListener = new ModifyListener() {
		public void modifyText(ModifyEvent e) {
			textChanged();
		}
	};

	private List<String> findHistory;
	private boolean historyDirty = true;
	private ToolItem bNext;
	private ToolItem bPrev;
	private SearchRule rule;
	private Match[] result = Match.EMPTY;

	private int preferredHeight;
	private int preferredWidth;

}
